//===--- SerializedModuleLoader.h - Import Swift modules --------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#ifndef POLARPHP_SERIALIZATION_MODULELOADER_H
#define POLARPHP_SERIALIZATION_MODULELOADER_H

#include "polarphp/ast/FileUnit.h"
#include "polarphp/ast/Module.h"
#include "polarphp/ast/ModuleLoader.h"
#include "llvm/Support/MemoryBuffer.h"

namespace polar {

class ModuleFile;

/// Spceifies how to load modules when both a module interface and serialized
/// AST are present, or whether to disallow one format or the other altogether.
enum class ModuleLoadingMode {
   PreferInterface,
   PreferSerialized,
   OnlyInterface,
   OnlySerialized
};

/// Common functionality shared between \c SerializedModuleLoader,
/// \c ModuleInterfaceLoader and \c MemoryBufferSerializedModuleLoader.
class SerializedModuleLoaderBase : public ModuleLoader {
   /// A { module, generation # } pair.
   using LoadedModulePair = std::pair<std::unique_ptr<ModuleFile>, unsigned>;
   std::vector<LoadedModulePair> LoadedModuleFiles;

   SmallVector<std::unique_ptr<llvm::MemoryBuffer>, 2> OrphanedMemoryBuffers;

protected:
   AstContext &Ctx;
   ModuleLoadingMode LoadMode;
   bool IgnorePolarphpSourceInfoFile;
   SerializedModuleLoaderBase(AstContext &ctx, DependencyTracker *tracker,
                              ModuleLoadingMode LoadMode,
                              bool IgnorePolarphpSourceInfoFile);

   void collectVisibleTopLevelModuleNamesImpl(SmallVectorImpl<Identifier> &names,
                                              StringRef extension) const;

   using AccessPathElem = std::pair<Identifier, SourceLoc>;
   bool findModule(AccessPathElem moduleID,
                   SmallVectorImpl<char> *moduleInterfacePath,
                   std::unique_ptr<llvm::MemoryBuffer> *moduleBuffer,
                   std::unique_ptr<llvm::MemoryBuffer> *moduleDocBuffer,
                   std::unique_ptr<llvm::MemoryBuffer> *moduleSourceInfoBuffer,
                   bool &isFramework, bool &isSystemModule);

   /// Attempts to search the provided directory for a loadable serialized
   /// .swiftmodule with the provided `ModuleFilename`. Subclasses must
   /// override this method to perform their custom module lookup behavior.
   ///
   /// If such a module could not be loaded, the subclass must return a
   /// `std::error_code` indicating the failure. There are two specific error
   /// codes that will be treated specially:
   /// - `errc::no_such_file_or_directory`: The module loader will stop looking
   ///   for loadable modules and will diagnose the lookup failure.
   /// - `errc::not_supported`: The module loader will stop looking for loadable
   ///   modules and will defer to the remaining module loaders to look up this
   ///   module.
   virtual std::error_code findModuleFilesInDirectory(
      AccessPathElem ModuleID, StringRef DirPath, StringRef ModuleFilename,
      StringRef ModuleDocFilename,
      StringRef ModuleSourceInfoFilename,
      SmallVectorImpl<char> *ModuleInterfacePath,
      std::unique_ptr<llvm::MemoryBuffer> *ModuleBuffer,
      std::unique_ptr<llvm::MemoryBuffer> *ModuleDocBuffer,
      std::unique_ptr<llvm::MemoryBuffer> *ModuleSourceInfoBuffer) = 0;

   std::error_code
   openModuleFiles(AccessPathElem ModuleID,
                   StringRef ModulePath, StringRef ModuleDocPath,
                   StringRef ModuleSourceInfoName,
                   std::unique_ptr<llvm::MemoryBuffer> *ModuleBuffer,
                   std::unique_ptr<llvm::MemoryBuffer> *ModuleDocBuffer,
                   std::unique_ptr<llvm::MemoryBuffer> *ModuleSourceInfoBuffer);

   std::error_code
   openModuleDocFile(AccessPathElem ModuleID,
                     StringRef ModuleDocPath,
                     std::unique_ptr<llvm::MemoryBuffer> *ModuleDocBuffer);

   void
   openModuleSourceInfoFileIfPresent(
      AccessPathElem ModuleID,
      StringRef ModulePath,
      StringRef ModuleSourceInfoFileName,
      std::unique_ptr<llvm::MemoryBuffer> *ModuleSourceInfoBuffer);

   /// If the module loader subclass knows that all options have been tried for
   /// loading an architecture-specific file out of a swiftmodule bundle, try
   /// to list the architectures that \e are present.
   ///
   /// \returns true if an error diagnostic was emitted
   virtual bool maybeDiagnoseTargetMismatch(SourceLoc sourceLocation,
                                            StringRef moduleName,
                                            StringRef archName,
                                            StringRef directoryPath) {
      return false;
   }

   /// Determines if the provided path is a cached artifact for dependency
   /// tracking purposes.
   virtual bool isCached(StringRef DepPath) {
      return false;
   }

public:
   virtual ~SerializedModuleLoaderBase();
   SerializedModuleLoaderBase(const SerializedModuleLoaderBase &) = delete;
   SerializedModuleLoaderBase(SerializedModuleLoaderBase &&) = delete;
   SerializedModuleLoaderBase &operator=(const SerializedModuleLoaderBase &) = delete;
   SerializedModuleLoaderBase &operator=(SerializedModuleLoaderBase &&) = delete;

   /// Attempt to load a serialized AST into the given module.
   ///
   /// If the AST cannot be loaded and \p diagLoc is present, a diagnostic is
   /// printed. (Note that \p diagLoc is allowed to be invalid.)
   FileUnit *loadAST(ModuleDecl &M, Optional<SourceLoc> diagLoc,
                     StringRef moduleInterfacePath,
                     std::unique_ptr<llvm::MemoryBuffer> moduleInputBuffer,
                     std::unique_ptr<llvm::MemoryBuffer> moduleDocInputBuffer,
                     std::unique_ptr<llvm::MemoryBuffer> moduleSourceInfoInputBuffer,
                     bool isFramework, bool treatAsPartialModule);

   /// Check whether the module with a given name can be imported without
   /// importing it.
   ///
   /// Note that even if this check succeeds, errors may still occur if the
   /// module is loaded in full.
   virtual bool canImportModule(std::pair<Identifier, SourceLoc> named) override;

   /// Import a module with the given module path.
   ///
   /// \param importLoc The location of the 'import' keyword.
   ///
   /// \param path A sequence of (identifier, location) pairs that denote
   /// the dotted module name to load, e.g., AppKit.NSWindow.
   ///
   /// \returns the module referenced, if it could be loaded. Otherwise,
   /// emits a diagnostic and returns a FailedImportModule object.
   virtual ModuleDecl *
   loadModule(SourceLoc importLoc,
              ArrayRef<std::pair<Identifier, SourceLoc>> path) override;


   virtual void loadExtensions(NominalTypeDecl *nominal,
                               unsigned previousGeneration) override;

   // @todo
//   virtual void loadObjCMethods(
//      ClassDecl *classDecl,
//      ObjCSelector selector,
//      bool isInstanceMethod,
//      unsigned previousGeneration,
//      llvm::TinyPtrVector<AbstractFunctionDecl *> &methods) override;

   virtual void verifyAllModules() override;
};

/// Imports serialized Polarphp modules into an AstContext.
class SerializedModuleLoader : public SerializedModuleLoaderBase {

   SerializedModuleLoader(AstContext &ctx, DependencyTracker *tracker,
                          ModuleLoadingMode loadMode, bool IgnorePolarphpSourceInfo)
      : SerializedModuleLoaderBase(ctx, tracker, loadMode, IgnorePolarphpSourceInfo)
   {}

   std::error_code findModuleFilesInDirectory(
      AccessPathElem ModuleID, StringRef DirPath, StringRef ModuleFilename,
      StringRef ModuleDocFilename,
      StringRef ModuleSourceInfoFilename,
      SmallVectorImpl<char> *ModuleInterfacePath,
      std::unique_ptr<llvm::MemoryBuffer> *ModuleBuffer,
      std::unique_ptr<llvm::MemoryBuffer> *ModuleDocBuffer,
      std::unique_ptr<llvm::MemoryBuffer> *ModuleSourceInfoBuffer) override;

   bool maybeDiagnoseTargetMismatch(SourceLoc sourceLocation,
                                    StringRef moduleName,
                                    StringRef archName,
                                    StringRef directoryPath) override;

public:
   virtual ~SerializedModuleLoader();

   /// Append visible module names to \p names. Note that names are possibly
   /// duplicated, and not guaranteed to be ordered in any way.
   void collectVisibleTopLevelModuleNames(
      SmallVectorImpl<Identifier> &names) const override;

   /// Create a new importer that can load serialized Swift modules
   /// into the given AstContext.
   static std::unique_ptr<SerializedModuleLoader>
   create(AstContext &ctx, DependencyTracker *tracker = nullptr,
          ModuleLoadingMode loadMode = ModuleLoadingMode::PreferSerialized,
          bool IgnorePolarphpSourceInfo = false) {
      return std::unique_ptr<SerializedModuleLoader>{
         new SerializedModuleLoader(ctx, tracker, loadMode, IgnorePolarphpSourceInfo)
      };
   }
};

/// Imports serialized Swift modules from a MemoryBuffer into an AstContext.
/// This interface is primarily used by LLDB.
class MemoryBufferSerializedModuleLoader : public SerializedModuleLoaderBase {
   llvm::StringMap<std::unique_ptr<llvm::MemoryBuffer>> MemoryBuffers;

   MemoryBufferSerializedModuleLoader(AstContext &ctx,
                                      DependencyTracker *tracker,
                                      ModuleLoadingMode loadMode,
                                      bool IgnorePolarphpSourceInfo)
      : SerializedModuleLoaderBase(ctx, tracker, loadMode,
                                   IgnorePolarphpSourceInfo) {}

   std::error_code findModuleFilesInDirectory(
      AccessPathElem ModuleID, StringRef DirPath, StringRef ModuleFilename,
      StringRef ModuleDocFilename,
      StringRef ModuleSourceInfoFilename,
      SmallVectorImpl<char> *ModuleInterfacePath,
      std::unique_ptr<llvm::MemoryBuffer> *ModuleBuffer,
      std::unique_ptr<llvm::MemoryBuffer> *ModuleDocBuffer,
      std::unique_ptr<llvm::MemoryBuffer> *ModuleSourceInfoBuffer) override;

   bool maybeDiagnoseTargetMismatch(SourceLoc sourceLocation,
                                    StringRef moduleName,
                                    StringRef archName,
                                    StringRef directoryPath) override;

public:
   virtual ~MemoryBufferSerializedModuleLoader();

   bool canImportModule(std::pair<Identifier, SourceLoc> named) override;
   ModuleDecl *
   loadModule(SourceLoc importLoc,
              ArrayRef<std::pair<Identifier, SourceLoc>> path) override;

   /// Register a memory buffer that contains the serialized module for the given
   /// access path. This API is intended to be used by LLDB to add swiftmodules
   /// discovered in the __swift_ast section of a Mach-O file (or the .swift_ast
   /// section of an ELF file) to the search path.
   ///
   /// FIXME: make this an actual access *path* once submodules are designed.
   void registerMemoryBuffer(StringRef AccessPath,
                             std::unique_ptr<llvm::MemoryBuffer> input) {
      MemoryBuffers[AccessPath] = std::move(input);
   }

   void collectVisibleTopLevelModuleNames(
      SmallVectorImpl<Identifier> &names) const override {}

   /// Create a new importer that can load serialized Swift modules
   /// into the given AstContext.
   static std::unique_ptr<MemoryBufferSerializedModuleLoader>
   create(AstContext &ctx, DependencyTracker *tracker = nullptr,
          ModuleLoadingMode loadMode = ModuleLoadingMode::PreferSerialized,
          bool IgnorePolarphpSourceInfo = false) {
      return std::unique_ptr<MemoryBufferSerializedModuleLoader>{
         new MemoryBufferSerializedModuleLoader(ctx, tracker, loadMode,
                                                IgnorePolarphpSourceInfo)};
   }
};


/// A file-unit loaded from a serialized AST file.
class SerializedAstFile final : public LoadedFile {
   friend class SerializedModuleLoaderBase;
   friend class SerializedSILLoader;
   friend class ModuleFile;

   ModuleFile &File;

   bool IsSIB;

   SerializedAstFile(ModuleDecl &M, ModuleFile &file, bool isSIB = false)
      : LoadedFile(FileUnitKind::SerializedAST, M), File(file), IsSIB(isSIB) {}

   void
   collectLinkLibrariesFromImports(ModuleDecl::LinkLibraryCallback callback) const;

public:
   bool isSIB() const { return IsSIB; }

   /// Returns the language version that was used to compile the contents of this
   /// file.
   const version::Version &getLanguageVersionBuiltWith() const;

   virtual bool isSystemModule() const override;

   virtual void lookupValue(DeclName name, NLKind lookupKind,
                            SmallVectorImpl<ValueDecl*> &results) const override;

   virtual StringRef
   getFilenameForPrivateDecl(const ValueDecl *decl) const override;

   virtual TypeDecl *lookupLocalType(StringRef MangledName) const override;

   virtual OpaqueTypeDecl *
   lookupOpaqueResultType(StringRef MangledName) override;

   virtual TypeDecl *
   lookupNestedType(Identifier name,
                    const NominalTypeDecl *parent) const override;

   virtual OperatorDecl *lookupOperator(Identifier name,
                                        DeclKind fixity) const override;

   virtual PrecedenceGroupDecl *
   lookupPrecedenceGroup(Identifier name) const override;

   virtual void lookupVisibleDecls(ModuleDecl::AccessPathTy accessPath,
                                   VisibleDeclConsumer &consumer,
                                   NLKind lookupKind) const override;

   virtual void lookupClassMembers(ModuleDecl::AccessPathTy accessPath,
                                   VisibleDeclConsumer &consumer) const override;

   virtual void
   lookupClassMember(ModuleDecl::AccessPathTy accessPath, DeclName name,
                     SmallVectorImpl<ValueDecl*> &decls) const override;

   /// Find all Objective-C methods with the given selector.
   // @todo
//   void lookupObjCMethods(
//      ObjCSelector selector,
//      SmallVectorImpl<AbstractFunctionDecl *> &results) const override;

   Optional<CommentInfo> getCommentForDecl(const Decl *D) const override;

   Optional<StringRef> getGroupNameForDecl(const Decl *D) const override;


   Optional<StringRef> getSourceFileNameForDecl(const Decl *D) const override;

   Optional<unsigned> getSourceOrderForDecl(const Decl *D) const override;

   Optional<StringRef> getGroupNameByUSR(StringRef USR) const override;

   Optional<BasicDeclLocs> getBasicLocsForDecl(const Decl *D) const override;

   void collectAllGroups(std::vector<StringRef> &Names) const override;

   virtual void getTopLevelDecls(SmallVectorImpl<Decl*> &results) const override;

   virtual void
   getTopLevelDeclsWhereAttributesMatch(
      SmallVectorImpl<Decl*> &Results,
      llvm::function_ref<bool(DeclAttributes)> matchAttributes) const override;

   virtual void
   getPrecedenceGroups(SmallVectorImpl<PrecedenceGroupDecl*> &Results) const override;

   virtual void
   getLocalTypeDecls(SmallVectorImpl<TypeDecl*> &results) const override;
   virtual void
   getOpaqueReturnTypeDecls(SmallVectorImpl<OpaqueTypeDecl*> &results) const override;

   virtual void getDisplayDecls(SmallVectorImpl<Decl*> &results) const override;

   virtual void
   getImportedModules(SmallVectorImpl<ModuleDecl::ImportedModule> &imports,
                      ModuleDecl::ImportFilter filter) const override;

   virtual void
   collectLinkLibraries(ModuleDecl::LinkLibraryCallback callback) const override;

   Identifier getDiscriminatorForPrivateValue(const ValueDecl *D) const override;

   virtual StringRef getFilename() const override;

   ClassDecl *getMainClass() const override;

   bool hasEntryPoint() const override;

   virtual const clang::Module *getUnderlyingClangModule() const override;

   virtual bool getAllGenericSignatures(
      SmallVectorImpl<GenericSignature> &genericSignatures)
   override;

   static bool classof(const FileUnit *file) {
      return file->getKind() == FileUnitKind::SerializedAST;
   }
   static bool classof(const DeclContext *DC) {
      return isa<FileUnit>(DC) && classof(cast<FileUnit>(DC));
   }
};

} // end namespace polar

#endif
